/**
 * 
 */
package fluo.rtp;

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Header;

/** Reads and MP3 file and splits it up into frames.
 *  You can use ReadNext to read the next set of bytes in the queue
 *  Or you can use getFrame(int) to get a specific byte frame.
 * @author maherbeg
 *
 * Created: Apr 14, 2009
 * Modified: Apr 14, 2009
 */
public class MP3Object {

	byte[] mp3;
	//TODO Throw an exception about not being able to read the MP3 Object
	//     which passes it on to the parent, who can handle the stream cancel
	//     and notify the user
	//Let's go ahead and open an mp3 file
	ArrayList<byte[]> frames;
	
	public MP3Object(String file) throws IOException {
		DataInputStream buffer = null;
		buffer = new DataInputStream(new FileInputStream(file));
		mp3 = new byte[buffer.available()];
		buffer.readFully(mp3);
		
		//Now let's split that thing up into manageable frames
		frames = new ArrayList<byte[]>();
		
		System.out.println("MP3 file length: " + (mp3.length / 1024.0) / 1024 + " MB");
		//Let's iterate through and split this baby up
		for(int i=0; i < mp3.length; i++){

			if((i+1) < mp3.length && //make sure we won't go over the limit
					((int)mp3[i]&0xFF) == 0xFF && ((int)mp3[i+1] & 0xF0) == 0xF0) {//First 11 bits of frame are 1's
				int curframe = frames.size()+1;
				
				//We have found the frame sync, let's check the next few id's
				if((i+3) >= mp3.length){
					System.out.println("MP3Object.MP3Object(" + curframe + "): MP3 header went out of bounds");
					break;
				}
				
				int mp3header[] = {mp3[i], mp3[i+1], mp3[i+2], mp3[i+3]}; //32 bit mp3 header
				for(int x = 0; x < mp3header.length; x++)
					mp3header[x] = ((int)mp3header[x])&0xFF;
				
				if(((int)mp3header[2] & 0xF0) == 0xF0){ //uh oh, string of 11 1's possibly happened, this is illegal
					//System.out.println("MP3Object.MP3Object(" + curframe + "): Bad header, bit rate determined as bad.");
					continue;
				}
				i+=3; //move the counter forward 3 bytes
				
				short checksum = 0; boolean checksumfound = false;
				if( (mp3header[1] & 0x01) == 0){ //Check sum bytes, check sum exists
					//System.out.println("MP3Object.MP3Object(" + curframe + "): CRC Found for this frame");
					i++; //move past mp3 header
					checksum = (short)(((short)mp3[i] << 8) + mp3[i+1]);
					checksumfound = true;
					i+=1; //move 1 byte (i increases by one at the end
				}
				
				//Find the bit rate, indexes from a table which is based on the layer and version
				// Source: http://www.mpgedit.org/mpgedit/mpeg_format/MP3Format.html
				//Our table is slightly different, mostly because i wanted to do fast look ups
				//row is the value in the mp3 header [15,12], then index columns 
				//by (mpeg v2=1,mpegv1=0)<<2+(Layer I=11, Layer2=10, Layer3=01, Reserved=00)
				//which should give you a value from 0-8, if somehow it is anything bigger, you
				//will go out of bounds
										//	V1,RV V1,L3, V1,L2, V1,L1, V2,RV, V2,L3, V2,L2, V2,L1
				short bitratetable[][] = { 	{0,		0xFFF,0xFFF,0xFFF,	0,	0xFFF,	0xFFF,0xFFF}, //first row is free
											{0,		32,		32,	  32,   0,		8,		8,	 32},
											{0,		40,		48,	  64,	0,		16,		16,	 48},
											{0,		48,	    56,   96,   0,		24,		24,  56},
											{0,		56,		64,	 128,	0,		32,		32,	 64},
											{0,		64,		80,	 160,	0,		40,		40,	 80},
											{0,		80,		96,	 192,	0,		48,		48,  96},
											{0,		96,		112, 224,	0,		56,		56,	112},
											{0,		112,	128, 256,	0,		64,		64,	128},
											{0,		128,	160, 288,	0,		80,		80, 144},
											{0,		160,	192, 320,	0,		96,		96, 160},
											{0,		192,	224, 352,	0,		112,   112, 176},
											{0,		224,	256, 384,	0,		128,   128, 192},
											{0,		256,	320, 416,	0,		144,   144, 224},
											{0, 	320,	384, 448,	0,		160,   160, 256},
											{0,		0,		0,	   0,	0,		  0,	 0,   0} //bad values
										};
				byte MPEGVersion = (byte)((mp3header[1] >> 3) & 0x03);
				if(MPEGVersion == 2) MPEGVersion = 1; //Helps to create the bit index, does not represent actual version, check table on website
				else				 MPEGVersion = 0;
				byte layer = (byte)((mp3header[1] >> 1) & 0x03);
				byte bitrateindex = (byte)((MPEGVersion << 2) + layer);
				if(bitrateindex > 8){
					System.out.println("MP3Object.MP3Object(" + curframe + "): Erroneous frame, can't find bit rate");
					continue;
				}
				
				short BitRate = bitratetable[(mp3header[2] >> 4)&0x0F][bitrateindex];
				if(BitRate == 0){
					System.out.println("MP3Object.MP3Object(" + curframe + "): Invalid bit rate of 0");
					continue;
				}
				
				if(BitRate == 0xFFF){
					System.out.println("MP3Object.MP3Object(" + curframe + "): BIT RATE IS FREE, CAN NOT HANDLE THIS TYPE OF FRAME.");
					continue;
				}
				
				//sampling frequency is indexed by row = bits[11,10] in the mp3 header, and 
				//columns = MPEGV2.5=00, reserved=01, MPEGV2=10, MPEGV1=11
				int  sampleratetable[][] = { {11025, 0, 22050, 44100},
											 {12000, 0, 24000, 48000},
											 { 8000, 0, 16000, 32000},
											 {    0, 0,		0,	   0} //reserved state
											};
				
				int SampleRate = sampleratetable[ (mp3header[2] >> 2 & 0x03)][ (mp3header[1] >> 3) & 0x03];
				if(SampleRate == 0){
					System.out.println("MP3Object.MP3Object(" + curframe + "): Invalid sample rate...");
					continue;
				}
				int Padding = (mp3header[2] >> 1) & 0x01;
				if(Padding == 1){
					if(layer == 3){ //Layer I files have a 32 bit padding
						Padding = 4;
					} else{
						Padding = 1; //Layer II & III have an 8 bit padding
					}
				}
				//FrameSize = 144 * BitRate / (SampleRate + Padding)
				//source: http://www.hydrogenaudio.org/forums/lofiversion/index.php/t5956.html
				if( (SampleRate + Padding) == 0){
					System.out.println("MP3Object.MP3Object(" + curframe + "): Divide by zero calculation Frame size");
					continue;
				}
				//				MPEG 1 	MPEG 2 	MPEG 2.5
				//				Layer I 	384 	384 	384
				//				Layer II 	1152 	1152 	1152
				//				Layer III 	1152 	576 	576
				short samplesPerFrameTable[][] = { 	{384,		384,		384}, //first row is free
													{1152,		1152,		1152},
													{1152,		576,		576}
												 };
													
				int samplesPerFrame = samplesPerFrameTable[3-layer][MPEGVersion];
				int slotSize;
				if(samplesPerFrame == 384)		slotSize = 4;
				else							slotSize = 1;

				
				//Subtract four for the header
				int FrameSize = (int)(((samplesPerFrame / 8 * (BitRate*1000)) / SampleRate) + Padding) * slotSize-4;
				System.out.println("MP3Object.MP3Object(): Sample Rate: " + SampleRate + ", Bit Rate: " + BitRate + ", Frame Size: "+FrameSize);
				
				Crc16 check = new Crc16();
				
				byte mp3frame[] = new byte[FrameSize];
				i++; //start byte from after checksum
				int EndFrame = i+FrameSize;
				for(int j=0; i < EndFrame && i < mp3.length; i++,j++){
					mp3frame[j] = mp3[i];
					check.add_bits(mp3frame[j], 8);
				}
				i--; //we went past the end of the frame to exit the loop, so let's start earlier.
				
				int calculated = check.checksum();
				System.out.print("MP3Object.MP3Object(" + curframe + "): Frame Header: " + 
						Integer.toHexString(mp3header[0]) + Integer.toHexString(mp3header[1])
						    + Integer.toHexString(mp3header[2]) + Integer.toHexString(mp3header[3])
							+ " Length of MP3 File " + frames.size() * 1152.0 /SampleRate 
							+ " s, Calculated CRC: " + calculated + " - header checksum: "+ checksum);
				
				if(checksumfound && (checksum != calculated)){
					System.out.println("...Erroneous check sum found...skipping frame.");
					continue;
				}
				
				//copy header into a full frame
				byte fullframe[] = new byte[FrameSize+4];
				for(int index=0; index<4; index++)
					fullframe[index] = (byte)mp3header[index];
				
				for(int index=4; index < fullframe.length; index++)
					fullframe[index] = mp3frame[index-4];
				
				frames.add(fullframe); //just for seeing number of frames
				//calculate the track length for testing purposes
				//http://www.hydrogenaudio.org/forums/lofiversion/index.php/t43172.html
				//numberOfFrames * noOfSamplesPerFrame / SamplingRate
				System.out.println(" we're legit.");
			}
		}
		System.out.println("MP3Object.MP3Object(): Number of good frames found: " + frames.size());
	}
	
	public byte[] getFrame(int index){
		return frames.get(index);
	}
	
	public int getSize(){
		return frames.size();
	}
	
	public static void main(String args[]) throws IOException, BitstreamException{
		MP3Object yo = new MP3Object("/Users/maherbeg/Music/iTunes/iTunes Music/Aesop Rock/Labor Days/01 Labor.mp3");
		/*Bitstream bs = new Bitstream(new FileInputStream("/Users/maherbeg/Music/iTunes/iTunes Music/Aesop Rock/Labor Days/01 Labor.mp3"));
		
		Header h = bs.readFrame();
		byte frame[] = null;
		int frameCount = 0;
		
		do{
			if(frameCount % 10 == 0)
				System.out.println(frameCount + ": " + h.bitrate_string() + " " + h.sample_frequency_string());
			frameCount++;
			//System.out.println(h.nSlots + " " + h.framesize);
			//all bytes of the frame
			for(int i=0; i < h.slots(); i++){
				bs.get_bits(8);
			}
			bs.closeFrame();
		}while( ( h = bs.readFrame()) != null);*/
	}
	
}
